Passer de la programmation séquentielle sur CPU à celle sur GPU exige un changement de paradigme : du traitement élément par élément vers l'exécution par blocs. Nous ne voyons plus les données comme un flux de scalaires, mais comme des collections de "blocs" planifiés pour exploiter pleinement la bande passante matérielle.
1. Contrainte mémoire vs. Contrainte calcul
Le goulot d'étranglement d'un noyau est déterminé par le rapport entre les opérations mathématiques et les accès mémoire. L'addition vectorielle est souvent limitée par la mémoire car elle effectue une seule addition pour chaque trois opérations mémoire (2 chargements, 1 stockage). Le matériel passe plus de temps à attendre la DRAM qu'à calculer.
2. Le rôle de BLOCK_SIZE
BLOCK_SIZE définit le niveau de granularité de la parallélisation. Si elle est trop petite, nous sous-utilisons les larges voies d'exécution de la GPU. Une taille optimale assure suffisamment de "travail en cours" pour saturer la mémoire.
3. Masquage de la latence grâce à l'occupation
Occupation est le nombre de blocs actifs sur la GPU. Bien que ce ne soit pas l'objectif ultime, cela permet au planificateur d'insérer un nouveau bloc pour effectuer des calculs tandis qu'un autre attend les récupérations de mémoire à haute latence depuis la VRAM.
4. Utilisation du matériel
Pour maximiser les performances, nous devons aligner notre BLOCK_SIZE avec les règles d'agrégation mémoire de l'architecture GPU, en garantissant que les threads consécutifs accèdent à des adresses mémoire consécutives.